﻿using System;
using System.Web;
using System.Net;
using System.IO;
using System.Collections.Specialized;
using System.Text.RegularExpressions;

namespace Net.Chirash.Twitter.TwitterClient
{
    public class TwitterConnection : OAuthBase
    {
        #region Constructors

        public TwitterConnection()
        {
        }

        public TwitterCommand CreateTwitterCommand()
        {
            return new TwitterCommand() { Connection = this };
        }

        #endregion

        #region Public Properties

        public enum Method { GET, POST };

        public string ConsumerKey { get; set; }

        public string ConsumerSecret { get; set; }

        public string Token { get; set; }

        public string TokenSecret { get; set; }

        public string Pin { get; set; }

        public string OAuthToken { get; set; }

        #endregion

        #region Public Authentication Methods

        /// <summary>
        /// Using Access Token, Create Url to get authorized.
        /// </summary>
        /// <returns>Url to get authorized</returns>
        public string CreateAuthorizationUrl()
        {
            string result = "";
            NameValueCollection query = ParseQueryString(OAuthWebRequest(RequestTokenAddress));
            if (query["oauth_token"] != null)
            {
                OAuthToken = query["oauth_token"];
                result = AuthorizeAddress + string.Format("?oauth_token={0}", query["oauth_token"]);
            }
            return result;
        }

        /// <summary>
        /// Set TokenSecret from Token
        /// </summary>
        /// <param name="AccessToken"></param>
        public void SetAccessToken(string oAuthToken)
        {
            this.Token = oAuthToken;
            NameValueCollection query = ParseQueryString(OAuthWebRequest(AccessTokenAddress));
            if (query["oauth_token"] != null)
            {
                this.Token = query["oauth_token"];
            }
            if (query["oauth_token_secret"] != null)
            {
                this.TokenSecret = query["oauth_token_secret"];
            }
        }

        /// <summary>
        /// Set TokenSecret from PIN
        /// </summary>
        /// <param name="Pin">7 Digit</param>
        public void SetAccessTokenWithPin(string Pin)
        {
            this.Pin = Pin;
            this.Token = OAuthToken;
            NameValueCollection query = ParseQueryString(OAuthWebRequest(AccessTokenAddress));
            if (query["oauth_token"] != null)
            {
                this.Token = query["oauth_token"];
            }
            if (query["oauth_token_secret"] != null)
            {
                this.TokenSecret = query["oauth_token_secret"];
            }
        }

        #endregion

        #region Private Authentication Methods

        private void ApplyAuthentication(Method method, ref string url, ref string postData)
        {
            string outUrl = "";
            string querystring = "";

            //Setup postData for signing.
            //Add the postData to the querystring.
            
            //if (method == Method.POST)
            {
                if (postData.Length > 0)
                {
                    //Decode the parameters and re-encode using the oAuth UrlEncode method.
                    NameValueCollection qs = ParseQueryString(postData);
                    postData = "";
                    foreach (string key in qs.AllKeys)
                    {
                        if (postData.Length > 0)
                        {
                            postData += "&";
                        }
                        qs[key] = Uri.UnescapeDataString(qs[key]).Replace("+", "%20");
                        qs[key] = this.UrlEncode(qs[key]);
                        postData += key + "=" + qs[key];

                    }
                    if (url.IndexOf("?") > 0)
                    {
                        url += "&";
                    }
                    else
                    {
                        url += "?";
                    }
                    url += postData;
                }
            }

            Uri uri = new Uri(url);

            string nonce = this.GenerateNonce();
            string timeStamp = this.GenerateTimeStamp();
            string sig;
            if (string.IsNullOrEmpty(Pin))
            {
                //Generate Signature
                sig = this.GenerateSignature(uri,
                     this.ConsumerKey,
                     this.ConsumerSecret,
                     this.Token,
                     this.TokenSecret,
                     method.ToString(),
                     timeStamp,
                     nonce,
                     out outUrl,
                     out querystring);
            }
            else
            {
                sig = this.GenerateSignature(uri,
                         this.ConsumerKey,
                         this.ConsumerSecret,
                         this.Token,
                         this.TokenSecret,
                         method.ToString(),
                         timeStamp,
                         nonce,
                         this.Pin,
                         out outUrl,
                         out querystring);
            }
            querystring += "&oauth_signature=" + Uri.EscapeDataString(sig).Replace("+", "%20");

            //Convert the querystring to postData
            if (method == Method.POST)
            {
                postData = querystring;
                querystring = "";
            }

            if (querystring.Length > 0)
            {
                outUrl += "?";
            }

            // apply to arguments
            url = outUrl + querystring;
        }

        #endregion

        #region Public Request Methods

        public string OAuthWebRequest(string url)
        {
            return OAuthWebRequest(Method.GET, url);
        }

        public string OAuthWebRequest(string url, string query)
        {
            return OAuthWebRequest(Method.GET, url, query);
        }

        public string OAuthWebRequest(Method method, string url)
        {
            return OAuthWebRequest(method, url, "");
        }

        public string OAuthWebRequest(Method method, string url, string postData)
        {
            string ret = "";

            ApplyAuthentication(method, ref url, ref postData);

            ret = WebRequest(method, url, postData);
            return ret;
        }

        public Stream TwitterStreamRequest(string url)
        {
            string dummyData = "";

            ApplyAuthentication(Method.GET, ref url, ref dummyData);

            var cer = System.Security.Cryptography.X509Certificates.X509Certificate.CreateFromCertFile(@"twitter.cer");
            var webRequest = System.Net.WebRequest.Create(url) as HttpWebRequest ;
            webRequest.Method = "GET";
            webRequest.Timeout = 90 * 1000;
            webRequest.ServicePoint.Expect100Continue = false;
            webRequest.ClientCertificates.Add(cer);

            return this.WebResponseStreamGet(webRequest);

        }

        public string WebRequest(Method method, string url, string postData)
        {
            HttpWebRequest webRequest = null;
            string responseData = "";
            webRequest = System.Net.WebRequest.Create(url) as HttpWebRequest;
            webRequest.Method = method.ToString();
            webRequest.ServicePoint.Expect100Continue = false;
            webRequest.Timeout = 20000;

            if (method == Method.POST)
            {
                webRequest.ContentType = "application/x-www-form-urlencoded";

                //POST the data.
                using (StreamWriter writer = new StreamWriter(webRequest.GetRequestStream()))
                {
                    try
                    {
                        writer.Write(postData);
                    }
                    catch
                    {
                        throw;
                    }
                }
            }

            responseData = WebResponseGet(webRequest);

            webRequest = null;

            return responseData;

        }

        #endregion

        #region Public Response Methods

        public string WebResponseGet(HttpWebRequest webRequest)
        {
            StreamReader responseReader = null;
            string responseData = "";

            try
            {
                responseReader = new StreamReader(webRequest.GetResponse().GetResponseStream());
                responseData = responseReader.ReadToEnd();
            }
            catch
            {
                throw;
            }
            finally
            {
                webRequest.GetResponse().GetResponseStream().Close();
                responseReader.Close();
                responseReader = null;
            }

            return responseData;
        }

        public Stream WebResponseStreamGet(HttpWebRequest webRequest)
        {
            return webRequest.GetResponse().GetResponseStream();
        }

        public void Dispose()
        {
            Dispose();
            GC.SuppressFinalize(this);
        }
        #endregion

        #region Private Fields

        private const string RequestTokenAddress = "http://twitter.com/oauth/request_token";

        private const string AuthorizeAddress = "http://twitter.com/oauth/authorize";

        private const string AccessTokenAddress = "http://twitter.com/oauth/access_token";

        #endregion

        #region Private Method

        /// <summary>
        /// Pase Query String
        /// </summary>
        /// <param name="text">Text</param>
        /// <returns>Collection of query string</returns>
        private NameValueCollection ParseQueryString(string line)
        {
            string[] separators = new string[] { "&", "?" };
            NameValueCollection collection;
            Regex reg = new Regex(@"(?m)^\s*(?'name'\w+)\s*=\s*(?'value'.*)\s*(?=\r?$)");
            string[] parameters = line.Split(separators, StringSplitOptions.RemoveEmptyEntries);
            collection = new NameValueCollection(parameters.Length);
            foreach (string text in parameters)
            {
                if (reg.IsMatch(text))
                {
                    Match m = reg.Match(text);
                    collection.Add(m.Groups["name"].Value, m.Groups["value"].Value);
                }
            }
            return collection;
        }

        #endregion
    }
}